import { FormContextKey, FormContextOptions } from '../Form';
import { PropType, computed, defineComponent, inject, provide, ref, watch, watchEffect } from 'vue';
import { InnerPopupProps } from '../inner/InnerPopup';
import Popover from '../Popover';
import AsyncValidator from 'async-validator';

export interface FormItemContextProps {
    name?: string
}

export interface FormItemProps {
    inline?: boolean,
    name?: string,
    labelStyle?: object,
    label?: string,
    labelAlign?: 'start' | 'end' | 'center',
    rules?: {[key: string]: any},
    errorTransfer?: boolean
    errorAlign?: InnerPopupProps['align']
}

export const FormItemContextKey = Symbol('FormItemContextKey');

export default defineComponent({
    name: 'FormItem',
    props: {
        inline: {
            type: Boolean,
            default: undefined
        },
        name: {
            type: String,
        },
        labelStyle: {
            type: Object,
        },
        label: {
            type: String,
        },
        labelAlign: {
            type: String as PropType<FormItemProps['labelAlign']>,
        },
        rules: {
            type: Object as PropType<FormItemProps['rules']>,
        },
        errorTransfer: {
            type: Boolean,
            default: undefined
        },
        errorAlign: {
            type: String as PropType<InnerPopupProps['align']>,
            default: undefined
        }
    },
    setup (props, { slots}) {
        const error = ref(null);
        const ctx: FormContextOptions = inject(FormContextKey, null);
        let itemRef: any;
        const labelAlign = computed(() => props.labelAlign ?? 'center');
        const errorTransfer = props.errorTransfer ?? ctx?.errorTransfer ?? false;
        const errorAlign = props.errorAlign ?? ctx?.errorAlign ?? 'right';

        const name = props.name;
        const rules = ref([]);
        const isRequired = ref(false);

        watch(() => [ctx?.rules.value, props.rules], ([crules, prules]) => {
            const rs = crules ?? {};
            if (props.name) {
                rules.value = rs[props.name] ?? [];
            }
            if (prules) {
                rules.value = rules.value.concat(prules);
            }
            isRequired.value = rules.value.some(rule => rule.required);
        }, {
            immediate: true
        });

        const clazzName = computed(() => ({
            'cm-form-item': true,
            'cm-form-item-error': error.value,
            'cm-form-item-inline': props.inline || ctx?.inline,
            'cm-form-item-required': isRequired.value && props.label
        }));
        const check = async (v: any) => {
            if (itemRef) {
                const rect = itemRef.getBoundingClientRect();
                if (rect.width === 0 || rect.height === 0) {
                    return true;
                }
            }

            if (name && rules.value.length) {
                const descriptor = {
                    [`${name}`]: rules.value
                };
                const validator = new AsyncValidator(descriptor);
                const model = {
                    [`${name}`]: v
                };
                return new Promise((resolve) => {
                    validator.validate(model, { firstFields: true }, (errors) => {
                        if (errors) {
                            error.value = errors[0].message;
                            resolve(false);
                        } else {
                            error.value = null;
                            resolve(true);
                        }
                    });
                });
            }
            return true;
        };

        if (!props.name) {
            console.warn('formItem needs name property to check valid');
        }

        const clearError = () => {
            error.value = null;
        };

        props.name && ctx?.setCheck && ctx.setCheck(props.name, check);
        props.name && ctx?.setClearValid && ctx.setClearValid(props.name, clearError);

        provide(FormItemContextKey, {
            name: props.name
        });

        return () => <div class={clazzName.value}>
            <label class={{
                "cm-form-label": true,
                [`cm-form-label-${labelAlign.value}`]: true,
            }} style={{width: ctx?.labelWidth + 'px', ...props.labelStyle}}>{props.label}</label>
            {
                errorTransfer
                    ? <Popover class="cm-form-item-error-popover" arrow align={errorAlign} theme="error" disabled={!error.value} content={error.value}>
                        <div class="cm-form-item-element" ref={(el) => itemRef = el}>
                            {slots.default?.()}
                        </div>
                    </Popover>
                    : <div class="cm-form-item-element" ref={(el) => itemRef = el}>
                        {slots.default?.()}
                        <div class="cm-form-item-error-tip">{error.value}</div>
                    </div>
            }
        </div>;
    }
});
